package com.acme.bankapp.account.impl;

import com.acme.bankapp.account.AbstractAccount;
import com.acme.bankapp.account.AccountClosedException;
import com.acme.bankapp.account.AccountType;
import com.acme.bankapp.account.NotEnoughFundsException;
import com.acme.bankapp.account.OverDraftLimitExceededException;


/**
 * @author Andrey Alekov
 * Checking account with overdraft.
 */
public class CheckingAccount extends AbstractAccount {
	private SavingAccount overdraft;
	private double overdraftLimit;
	
	/**
	 *  Constructor
	 * 	@param amount the initial value.
	 *  @param overdraft the initial overdraft value.
	 */
	public CheckingAccount(double amount, double overdraft) {
		super(amount, createUniqueAccountId(), AccountType.CHECKING);
		this.overdraft = new SavingAccount(overdraft, createUniqueAccountId());
		this.overdraftLimit = overdraft;
	}
	
	/**
	 *  Constructor
	 * 	@param amount the initial value.
	 *  @param overdraft the initial overdraft value.
	 */
	public CheckingAccount(double amount, double overdraft, long accountId) {
		super(amount, accountId, AccountType.CHECKING);
		this.overdraft = new SavingAccount(overdraft, createUniqueAccountId());
		this.overdraftLimit = overdraft;
	}
	/**
	 * 	Get overdraft value.
	 *  @return overdraft value
	 */
	public double getOverdraft() {
		return this.overdraft.getBalance();
	}

	/* (non-Javadoc)
	 * @see com.acme.bankapp.account.AbstractAccount#getOverdraftAccount()
	 * Return allowed balance with overdraft.
	 */
	@Override
	public SavingAccount getOverdraftAccount() throws UnsupportedOperationException {
		return this.overdraft;
	}
	
	/**
	 * 	Set overdraft value.
	 *  @return overdraft balance value
	 */
	public void setOverdraft(double overdraft) {
		this.overdraft.setBalance(overdraft);;
	}

	/* (non-Javadoc)
	 * @see com.acme.bankapp.service.bank.AbstractAccount#maximumAmounToWithdraw()
	 * Return allowed balance with overdraft.
	 */
	@Override
	public double maximumAmountToWithdraw() {
		return (this.balance + this.overdraft.getBalance());
	}

	/*
	 * (non-Javadoc)
	 * @see com.acme.bankapp.account.AbstractAccount#deposit(double)
	 */
	@Override
	public void deposit(double amount) throws AccountClosedException {
		if (!isOpened()){
			throw new AccountClosedException("Account closed.");
		}
		this.balance += amount;
		double delta = this.overdraft.getBalance() - this.overdraftLimit;
		this.balance -= delta;
		this.overdraft.deposit(delta);
	}
	
	/*
	 * (non-Javadoc)
	 * @see com.acme.bankapp.account.AbstractAccount#withdraw(double)
	 */
	@Override
	public void withdraw(double amount) throws OverDraftLimitExceededException, 
	NotEnoughFundsException, AccountClosedException {
		if (!isOpened()){
			throw new AccountClosedException("Account closed.");
		}
		if ((this.balance + this.overdraft.getBalance()) >= Math.abs(amount)){
			if (this.balance > amount){
				this.balance -= amount;
				assert this.balance >= 0d;
			}
			if (this.balance == 0d){
				this.overdraft.withdraw(amount);
				assert this.overdraft.getBalance() >= 0d;
			}
			else{
				double overdrafted = this.balance - amount;
				this.balance = 0d;
				try {
					this.overdraft.withdraw(overdrafted);
				} catch (NotEnoughFundsException e) {
					throw new OverDraftLimitExceededException("Overdraft limit"
							+ " exceed. " + e.getMessage());
				}
				assert this.overdraft.getBalance() >= 0d;
			}
		}
		else{
			throw new OverDraftLimitExceededException("Overdraft limit "
					+ "exceeded. Avaliable balance " + (this.balance 
							+ this.overdraft.getBalance()));
		}
	}
	
	/*
	 * (non-Javadoc)
	 * @see com.acme.bankapp.account.AbstractAccount#toString()
	 */
	@Override
	public String toString() {
		return "Actual balance: " + this.balance + ", overdraft: " 
				+ this.overdraft.getBalance();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result
				+ ((overdraft == null) ? 0 : overdraft.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (!super.equals(obj)) {
			return false;
		}
		if (!(obj instanceof CheckingAccount)) {
			return false;
		}
		CheckingAccount other = (CheckingAccount) obj;
		if (overdraft == null) {
			if (other.overdraft != null) {
				return false;
			}
		} else if (!overdraft.equals(other.overdraft)) {
			return false;
		}
		return true;
	}

	/*
	* (non-Javadoc)
	* @see com.acme.bankapp.domain.bank.Account#hasOverdraft()
	*/
	@Override
	public boolean hasOverdraft() {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * @see com.acme.bankapp.account.AbstractAccount#setAccountType(com.acme.bankapp.account.AccountType)
	 */
	@Override
	public void setAccountType(AccountType accountType) 
			throws UnsupportedOperationException {
		if (accountType == AccountType.SAVING){
			throw new UnsupportedOperationException("Unsuported account type");
		}
		this.accountType = accountType;
	}
}